package serv_cmd

import (
	"context"
	"net"
	"time"

	"atomgit.com/openlinksaas/proto-gen-go.git/swarm_proxy_api"
	"atomgit.com/openlinksaas/swarm_api_proxy/utils"
	"github.com/docker/docker/api/types"
	"github.com/google/uuid"
)

type TermItem struct {
	TermId       string
	ExecId       string
	Cancel       context.CancelFunc
	Conn         net.Conn
	OutChan      chan []byte
	LastReadTime int64
}

func (apiImpl *SwarmProxyApiImpl) OpenTerm(ctx context.Context, req *swarm_proxy_api.OpenTermRequest) (*swarm_proxy_api.OpenTermResponse, error) {
	//检查权限
	tokenInfo, err := apiImpl.getTokenInfo(apiImpl.cfg.LinkSaasAddr, req.Token)
	if err != nil {
		return &swarm_proxy_api.OpenTermResponse{
			Code:   swarm_proxy_api.OpenTermResponse_CODE_WRONG_TOKEN,
			ErrMsg: "错误的访问令牌",
		}, nil
	}
	c, err := utils.GetDockerClient(apiImpl.cfg.ServerUrl, apiImpl.cfg.CertPath)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	exist, err := apiImpl.existProject(ctx, c, tokenInfo.ProjectId)
	if err != nil {
		return nil, err
	}
	if !exist {
		return &swarm_proxy_api.OpenTermResponse{
			Code:   swarm_proxy_api.OpenTermResponse_CODE_NO_PERMISSION,
			ErrMsg: "没有权限",
		}, nil
	}

	if !tokenInfo.Admin {
		userPerm := apiImpl.getUserPerm(ctx, tokenInfo.ProjectId, tokenInfo.MemberUserId)
		if !userPerm.Exec {
			return &swarm_proxy_api.OpenTermResponse{
				Code:   swarm_proxy_api.OpenTermResponse_CODE_NO_PERMISSION,
				ErrMsg: "没有权限",
			}, nil
		}
	}

	_, err = c.ContainerInspect(ctx, req.ContainerId)
	if err != nil {
		return &swarm_proxy_api.OpenTermResponse{
			Code:   swarm_proxy_api.OpenTermResponse_CODE_NO_CONTAINER,
			ErrMsg: "容器不存在",
		}, nil
	}
	consoleSize := [2]uint{uint(req.TermHeight), uint(req.TermWidth)}
	execIdRes, err := c.ContainerExecCreate(ctx, req.ContainerId, types.ExecConfig{
		Tty:          true,
		ConsoleSize:  &consoleSize,
		AttachStdin:  true,
		AttachStderr: true,
		AttachStdout: true,
		Cmd:          []string{req.ShellCmd},
	})
	if err != nil {
		return nil, err
	}

	execCtx, cancel := context.WithCancel(context.Background())
	hijackedResp, err := c.ContainerExecAttach(execCtx, execIdRes.ID, types.ExecStartCheck{
		Tty:         true,
		ConsoleSize: &consoleSize,
	})
	if err != nil {
		cancel()
		return nil, err
	}
	termId := uuid.NewString()
	outChan := make(chan []byte, 100)
	apiImpl.termItemMap.Store(termId, &TermItem{
		TermId:       termId,
		ExecId:       execIdRes.ID,
		Cancel:       cancel,
		Conn:         hijackedResp.Conn,
		OutChan:      outChan,
		LastReadTime: time.Now().Unix(),
	})

	go apiImpl.readTerm(hijackedResp.Conn, outChan)
	return &swarm_proxy_api.OpenTermResponse{
		Code:   swarm_proxy_api.OpenTermResponse_CODE_OK,
		TermId: termId,
	}, nil
}

func (apiImpl *SwarmProxyApiImpl) readTerm(conn net.Conn, outChan chan<- []byte) {
	defer func() {
		recover()
	}()
	//conn会定时回收，不用在此关闭
	for {
		buf := make([]byte, 4096)
		n, err := conn.Read(buf)
		if n > 0 {
			outChan <- buf[:n]
		}
		if err != nil {
			break
		}
	}
}

func (apiImpl *SwarmProxyApiImpl) WriteTerm(ctx context.Context, req *swarm_proxy_api.WriteTermRequest) (*swarm_proxy_api.WriteTermResponse, error) {
	termItemValue, ok := apiImpl.termItemMap.Load(req.TermId)
	if !ok {
		return &swarm_proxy_api.WriteTermResponse{
			Code:   swarm_proxy_api.WriteTermResponse_CODE_NO_TERM,
			ErrMsg: "终端句柄不存在",
		}, nil
	}
	termItem := termItemValue.(*TermItem)
	_, err := termItem.Conn.Write(req.Data)
	if err != nil {
		return nil, err
	}
	return &swarm_proxy_api.WriteTermResponse{
		Code: swarm_proxy_api.WriteTermResponse_CODE_OK,
	}, nil
}

func (apiImpl *SwarmProxyApiImpl) ReadTerm(ctx context.Context, req *swarm_proxy_api.ReadTermRequest) (*swarm_proxy_api.ReadTermResponse, error) {
	defer func() {
		recover()
	}()

	termItemValue, ok := apiImpl.termItemMap.Load(req.TermId)
	if !ok {
		return &swarm_proxy_api.ReadTermResponse{
			Code:   swarm_proxy_api.ReadTermResponse_CODE_NO_TERM,
			ErrMsg: "终端句柄不存在",
			Data:   []byte{},
		}, nil
	}
	termItem := termItemValue.(*TermItem)
	termItem.LastReadTime = time.Now().Unix()
	apiImpl.termItemMap.Store(termItem.TermId, termItem)

	select {
	case data := <-termItem.OutChan:
		return &swarm_proxy_api.ReadTermResponse{
			Code: swarm_proxy_api.ReadTermResponse_CODE_OK,
			Data: data,
		}, nil
	case <-time.After(30 * time.Second):
		return &swarm_proxy_api.ReadTermResponse{
			Code: swarm_proxy_api.ReadTermResponse_CODE_OK,
			Data: []byte{},
		}, nil
	}
}

func (apiImpl *SwarmProxyApiImpl) SetTermSize(ctx context.Context, req *swarm_proxy_api.SetTermSizeRequest) (*swarm_proxy_api.SetTermSizeResponse, error) {
	termItemValue, ok := apiImpl.termItemMap.Load(req.TermId)
	if !ok {
		return &swarm_proxy_api.SetTermSizeResponse{
			Code:   swarm_proxy_api.SetTermSizeResponse_CODE_NO_TERM,
			ErrMsg: "终端句柄不存在",
		}, nil
	}
	termItem := termItemValue.(*TermItem)

	c, err := utils.GetDockerClient(apiImpl.cfg.ServerUrl, apiImpl.cfg.CertPath)
	if err != nil {
		return nil, err
	}
	defer c.Close()

	err = c.ContainerExecResize(ctx, termItem.ExecId, types.ResizeOptions{
		Height: uint(req.TermHeight),
		Width:  uint(req.TermWidth),
	})
	if err != nil {
		return nil, err
	}
	return &swarm_proxy_api.SetTermSizeResponse{
		Code: swarm_proxy_api.SetTermSizeResponse_CODE_OK,
	}, nil
}
